/**
 * 账号选择相关纯业务逻辑
 */
import { tuple, keywordCompare } from '@gnlab/utils'
import { useDebounce } from '@gnlab/utils/env-react'
import { OrderedSet } from 'immutable'
import services from '@/services'
import type { BaseAccount } from '@/services/auth'
import type { AccountGroup } from '@/services/features/accounts'

/**
 * 获取账号及分组数据
 */
export type AccountMap = Map<string, BaseAccount>

export interface AccountsData {
  map: AccountMap // 完整账号 Map
  groups: AccountGroup[] // 账号分组列表
}

interface AccountsDataOptions {
  withGroups?: boolean // 是否包含自定义账号分组（为 false 时只显示“全部账号”分组），默认为 true
}

export async function getAccountsData(options?: AccountsDataOptions): Promise<AccountsData> {
  const { withGroups = true } = options ?? {}

  const accountsReq = services.features.accounts.getAccounts()
  const groupsReq = withGroups ? services.features.accounts.getGroups() : Promise.resolve([]) // 若不需要”分组“功能，直接返回空的分组
  const [accounts, customGroups] = await Promise.all([accountsReq, groupsReq])

  const accountMap = new Map(accounts.map(account => [account.id, account]))

  const formattedCustomGroups = customGroups.map(group => ({
    ...group,
    accountIds: group.accountIds.filter(id => accountMap.has(id)), // 只保留 accountMap 里出现了的 account
  }))
  const groups = [
    { id: -1, name: '所有账号', accountIds: accounts.map(v => v.id) },
    ...formattedCustomGroups,
  ]

  return { map: accountMap, groups }
}

/**
 * 实现账号选择
 */
export interface ChoosingProps {
  data?: AccountsData
  withGroups?: boolean // 是否显示自定义账号分组（data 有值时以它为准，忽略此选项）
  initialChoosed?: null | string | string[] // 初始选中的账号 ID
  lockChoosed?: string | string[] // 固定选中的账号 ID，不能取消选择（这些账号不会出现在 choosed 结果里）
  disabled?: string | string[] // 不允许选中的账号 ID（一定不会出现在 choosed 里）
}

export type ChoosingData = ReturnType<typeof useChoosing>

export function useChoosing(options: ChoosingProps) {
  const {
    data: inputData,
    withGroups,
    initialChoosed = [],
    lockChoosed: rawLockChoosed = [],
    disabled: rawLockDisabled = [],
  } = options

  // 若未提供 inputData，通过 dataOptions 自行获取
  const [loadedData, setLoadedData] = useState<AccountsData | null>(null)
  useEffect(() => {
    if (inputData) return
    let cancelled = false
    void getAccountsData({ withGroups }).then(loadedData => {
      if (cancelled) return
      setLoadedData(loadedData)
    })
    return () => {
      cancelled = true
    }
  }, [inputData, withGroups])

  const accountsData = inputData ?? loadedData

  // 不可修改（选中/取消选中）的账号集合：Map<id => boolean>
  // true 固定选中, false 固定不选中
  const lockChoosed = Array.isArray(rawLockChoosed) ? rawLockChoosed : [rawLockChoosed]
  const lockDisabled = Array.isArray(rawLockDisabled) ? rawLockDisabled : [rawLockDisabled]
  const locked = new Map([
    ...lockChoosed.map(id => tuple(id, true)),
    ...lockDisabled.map(id => tuple(id, false)),
  ])

  // 初始化完成后，initialChoosed 的变化将无法再影响 choosed
  const [choosed, setChoosed] = useState(() =>
    OrderedSet(
      Array.isArray(initialChoosed)
        ? initialChoosed
        : initialChoosed !== null
        ? [initialChoosed]
        : [],
    ),
  )

  // 从 choosed 中过滤掉 locked 的和数据里没出现的账号
  const validChoosed = choosed.filter(id => {
    if (accountsData && !accountsData.map.has(id)) return false
    if (locked.has(id)) return false
    return true
  })

  return {
    choosed: validChoosed,
    setChoosed: setChoosed as (choosed: OrderedSet<string>) => void,
    locked, // 经过整理的 locked 数据
    accountsData, // 返回此 hook 加载到的账号数据；对于自行提供账号数据给此 hook 的使用者无用
  }
}

/**
 * 实现账号搜索
 */
interface SearchState {
  latestMap: AccountMap | null // 上一次接收到的完整账号表
  latestKeyword: string // 当前应用的搜索关键字
  searched: BaseAccount[] | null // 搜索得到的账号列表，为 null 代表未进行搜索
}

function searchReducer(
  state: SearchState,
  updates: { map?: AccountMap | null; keyword?: string },
): SearchState {
  const map = updates.map !== undefined ? updates.map : state.latestMap
  const keyword = updates.keyword ?? state.latestKeyword
  const searched =
    map && keyword
      ? [...map.values()].filter(account => keywordCompare(keyword, account.name))
      : null
  return { latestMap: map, latestKeyword: keyword, searched }
}

export function useSearch(accountMap: Map<string, BaseAccount> | null) {
  const [{ latestMap, searched }, updateSearch] = useReducer(searchReducer, {
    latestMap: accountMap,
    latestKeyword: '',
    searched: null,
  })

  // 若传入的 map 发生变化，重新生成搜索结果
  if (accountMap !== latestMap) updateSearch({ map: accountMap })

  // 文本框内容
  const [inputKeyword, setInputKeyword] = useState('')

  // 处理文本框更新
  const debounceUpdateSearch = useDebounce(updateSearch, 300, { leading: true })
  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    const newKeyword = e.target.value
    setInputKeyword(newKeyword)
    debounceUpdateSearch({ keyword: newKeyword }) // 延迟更新搜索结果
  }

  // 不经过 input，直接提供关键词执行搜索
  function search(newKeyword: string) {
    setInputKeyword(newKeyword) // 关键词同步到文本框
    updateSearch({ keyword: newKeyword })
  }

  return {
    keyword: inputKeyword,
    keywordOnChange: onChange,
    search,
    searchedAccounts: searched,
  }
}
